iT邦幫忙

2021 iThome 鐵人賽

DAY 5
2
AI & Data

手寫中文字之影像辨識系列 第 5

【第5天】資料前處理-去除雜訊與灰階

  • 分享至 

  • xImage
  •  

現況

  1. 清洗後的圖檔,部分仍有紅框等雜訊,或是中文字體顏色不同(藍色、黑色),如下圖。

  2. 若將含有不同顏色中文字或不同位置紅框的圖檔納入模型訓練,可能影響後續模型的辨識效果。(如:同一個中文字,因字體顏色是黑色或藍色,導致辨識結果錯誤)

  3. 我們選擇以HSV進行顏色追蹤,在儘可能保留中文字字跡的前提下,利用opencv mask功能去除紅框。此外,將圖檔以灰階圖與二值化圖呈現,比較兩者效果。

  4. 最終選擇以灰階圖作為訓練樣本,並將資料集以7:3比例分配成train與test,供後續訓練模型之用。


工具/套件

  1. opencv-python
  2. PIL(pillow)
  3. numpy
  4. shutil

內容

  1. HSV(Hue, Saturation, Value)

    1.1 HSV

    • 由色調(Hue)、飽和度(Saturation)、亮度(Value)三個分量組成,顏色分佈如下。

      圖片來自於:https://www.itread01.com/content/1549945446.html

    • HSV顏色空間,能更直觀的表示人眼對色彩的感受。

    • 基本上,HSV只要確定色調(1個分量),就可確定是何種顏色;一般的RGB,則是依照每Red、Green、Blue(3個分量)的比例,才能確定是何種顏色。

    1.2 顏色追蹤(HSV+opencv)

    • HSV便於表現特定的顏色,且opencv讀取的圖檔為RGB,可透過內建函數轉換成HSV。
    # 將RGB轉換成HSV顏色空間
    redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    • 轉換成HSV後,以H值鎖定色調後,只要調整表和度與亮度的閾值範圍,就可以追蹤特定顏色。因此,常用於分割特定顏色物件。(如:用來追蹤紅框,再以mask將紅框去除)
  2. HSV顏色追蹤與mask

    2.1 HSV調色盤-查詢HSV值

    • 程式碼
    import cv2
    import numpy as np
    
    # 讀取中文路徑圖檔(圖片讀取為BGR)
    def cv_imread(filePath):
        cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), -1)
        return cv_img
    
    # 點擊欲判定HSV值的圖片位置(以滑鼠左鍵單擊)
    def mouse_click(event, x, y, flags, para):
        if event == cv2.EVENT_LBUTTONDOWN:
            print("BGR:", img[y, x])
            print("GRAY:", gray[y, x])
            print("HSV:", hsv[y, x])
            print('='*30)
    
    if __name__ == '__main__':
        # 讀取圖檔
        img = cv_imread('./data/04-清洗標籤後圖片/清洗標籤final/可用/2_惠.jpg')
        img = cv2.resize(img, (320, 240))
        # 轉換成gray與HSV
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        cv2.namedWindow("img")
        cv2.setMouseCallback("img", mouse_click)
        while True:
            cv2.imshow('img', img)
            if cv2.waitKey() == ord('q'):
                break
        cv2.destroyAllWindows()
    
    • 效果

    2.2 鎖定HSV閾值與Demo:載入圖檔後,藉由滑動滾輪調整HSV值,逐一挑選出紅框、藍色字體、黑色字體的HSV閾值,示意圖如下。


    圖片來自於:https://blog.csdn.net/weixin_42216109/article/details/89520423

  3. 得到紅框、藍色字體、黑色字體HSV閾值後,開始去除雜訊,並比較灰階圖、二值化圖,程式碼如下。

    3.1 追蹤紅框、藍色字體、黑色字體

    • 紅框:因為紅框有2種,所以分別以red1_mask與red2_mask追蹤。
    import cv2
    import numpy as np
    import os
    from PIL import Image
    
    def red1_mask(img):
        # 紅1
        lower = np.array([150, 80, 94])
        upper = np.array([180, 255, 255])
        redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask1 = cv2.inRange(redhsv1, lower, upper)
        return mask1
    
    def red2_mask(img):
        # 紅2
        lower = np.array([0, 80, 89])
        upper = np.array([10, 255, 255])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    
    • 追蹤藍色中文字
    def blue_mask(img):
        # 藍
        lower = np.array([90, 43, 46])
        upper = np.array([124, 255, 255])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    
    • 追蹤黑色中文字
    def black_mask(img):
        # 黑
        lower = np.array([0, 0, 0])
        upper = np.array([255, 255, 135])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    

    3.2 設定膨脹函數

    def my_dilate(img):
        kernel = np.ones((3, 3), np.uint8)
        new_img = cv2.dilate(img, kernel, iterations=1)
        return new_img
    

    3.3 找圖檔中的眾數,用來取代紅色的mask區域

    def get_mode(img):
        # 閾值取眾數
        # bincount():統計非負整數的個數,不能統計浮點數
        counts = np.bincount(img.flatten())
        # counts的index代表出現的數,counts[index]代表出現數的次數
        # 今要求counts[index] 排序後最大跟第二大的counts的index(代表眾數跟出現第二多次的數)
        # 最後一個元素是counts最大值的index ,倒數第二是二大
        counts_sort = np.argsort(counts)
        index = counts_sort[-1]
        # 以防圖片出現大量黑色面積
        # 出現大量黑色區塊的話,取第二多數
        if index <= 100:
            index = counts_sort[-2]
            return index
        # 否則就return原本的眾數
        return index
    

    3.4 合併mask區域(刪去紅框、保留中文字)

    • 過程
    # 合併mask區域
    def process_img(img, turn=None):
        # 紅1
        mask1 = red1_mask(img)
        # 紅2
        mask2 = red2_mask(img)
        # 合併紅1+紅2之範圍
        mask3 = cv2.bitwise_or(mask1, mask2)
        # 膨脹mask3
        mask3 = my_dilate(mask3)
        # 黑白反轉
        mask3 = cv2.bitwise_not(mask3, mask3)
    
        # 圖檔轉換成灰階
        image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # 取眾數
        image_mode =get_mode(image)
        # 把紅色的mask區域換成眾數
        image[mask3 == 0] = image_mode
    
        # 顯示mask後圖片
        image = cv2.resize(image, (160, 120))
        cv2.imshow('mask', image)
        cv2.waitKey()
    
        # 高斯模糊後取眾數
        blur = cv2.GaussianBlur(image, (3, 3), 0)
        c = get_mode(blur) * 0.7
        # 二值化
        ret, thresh1 = cv2.threshold(image, c, 255, cv2.THRESH_BINARY)
        if turn == 'binary':
            thresh1 = cv2.resize(thresh1, (160, 120))
            cv2.imshow('binary', thresh1)
            cv2.waitKey()
            return thresh1
        if turn == 'gray':
            blur = cv2.resize(blur, (160, 120))
            cv2.imshow('gray', blur)
            cv2.waitKey()
            return blur
    if __name__ == '__main__':
        img = cv_imread('./data/04_清洗標籤後圖片/清洗標籤final/可用/2_惠.jpg')
        # 顯示原圖片
        img = cv2.resize(img, (160, 120))
        cv2.imshow('origin', img)
        cv2.waitKey()
        process_img(img, 'binary')
        process_img(img, 'gray')
    
    • 成果展示

      <原圖>

      <灰階圖>

      <二值化圖>

    3.5 雖然將圖檔二值化,訓練模型效率較高,但可能造成圖像信息嚴重丟失;灰階則可以兼顧訓練效率與保留較多的梯度信息。因此,我們最終選擇以灰階圖作為訓練樣本。

  4. 分配trian與test資料集:先統計每個中文字的照片張數,再以7:3比例分配成train與test。

    4.1 過程

    import os
    import random
    import shutil
    import os
    import shutil
    
    # 分成訓練集跟資料集
    src_dir_name = './train/'
    target_dir_name = './test/'
    test_size = 0.3
    labels = set(os.listdir(src_dir_name))
    
    def word_classfier():
        word_list_dir = []
        for i in os.listdir(src_dir_name):
            if i.endswith('.jpg'):
                word_list_dir.append(i.split('.')[0][-1])
    
        word_list_dir = set(word_list_dir)
        print(word_list_dir)
    
        for i in os.listdir(src_dir_name):
            if i.endswith('.jpg'):
                if i.split('.')[0][-1] in word_list_dir:
                    try:
                        os.mkdir(src_dir_name+i.split('.')[0][-1])
                    except FileExistsError:
                        pass
                    shutil.move(src_dir_name+i,src_dir_name+i.split('.')[0][-1]+'/'+i)
    
    def move_test_data(test_data:list):
        for i in test_data:
            word_subfolder = i.split('.')[0][-1]
            if word_subfolder in labels:
                print(src_dir_name+word_subfolder+'/'+i)
                try:
                    os.mkdir(target_dir_name +word_subfolder)
                except FileExistsError:
                    pass
                shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
            elif  word_subfolder not in labels:
                word_subfolder = i.split('.')[0][0]
                print(src_dir_name+word_subfolder+'/'+i)
                try:
                    os.mkdir(target_dir_name +word_subfolder)
                except FileExistsError:
                    pass
                try:
                    shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
                except FileNotFoundError:
                    shutil.move(src_dir_name + word_subfolder + '/' + i, target_dir_name + word_subfolder + '/' + i)
    
    def test_train_split():
        try:
            os.mkdir(target_dir_name)
        except FileExistsError:
            pass
        for i in os.listdir(src_dir_name):
            #每個字的照片數
            dir_length = len(os.listdir(src_dir_name+i))
            #3:7抽樣
            test_size = round(0.3 * dir_length)
            test_data = random.sample(os.listdir(src_dir_name+i), k=test_size)
            move_test_data(test_data)
    
    if __name__ == '__main__':
        # 把字分類成800個資料夾
        word_classfier()
        # 分成訓練集跟測試集
        test_train_split()
    

    4.2 成果

    • train與test

    • train資料夾內


小結

  1. 在辨識手寫中文字時,可能遭遇圖檔內字跡有部分缺失,導致無法正確辨識。因此,希望能在圖檔中加入椒鹽雜訊(Salt & Pepper Noise),再進行模型訓練,降低辨識錯誤的可能性。
  2. 下一章,是資料前處理的最後一步,目標是:「在圖檔中隨機加入椒鹽雜訊,並進行資料擴增」。

讓我們繼續看下去...


參考資料

  1. HSV顏色空間識別區域顏色
  2. HSV顏色識別-HSV基本颜色分量範圍
  3. HSV調色盤-查詢HSV值
  4. HSV調色盤-色彩追蹤與mask

上一篇
【第4天】資料前處理-圖檔分類與裁切
下一篇
【第6天】資料前處理-資料擴增
系列文
手寫中文字之影像辨識31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言